package com.kecheng.xinmei.service.impl;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.kecheng.xinmei.config.ETHConfig;
import com.kecheng.xinmei.dao.CoinDao;
import com.kecheng.xinmei.dao.OrderDao;
import com.kecheng.xinmei.dao.UserDao;
import com.kecheng.xinmei.entity.Coin;
import com.kecheng.xinmei.entity.Order;
import com.kecheng.xinmei.entity.OrderDTO;
import com.kecheng.xinmei.entity.User;
import com.kecheng.xinmei.model.*;
import com.kecheng.xinmei.service.UserService;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.FunctionReturnDecoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.Sign;
import org.web3j.crypto.StructuredDataEncoder;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.core.methods.response.EthCall;
import org.web3j.protocol.http.HttpService;
import org.web3j.utils.Numeric;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

/**
 * @author gaokecheng
 * @version 1.0
 * @date 2020/11/20 4:21 下午
 */
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    @Autowired
    private CoinDao coinDao;
    @Autowired
    private OrderDao orderDao;

    @Override
    public User getUser() {
        return userDao.getUser().get(0);

    }

    @Override
    public UserNftCollectionDTO getMyNFT(String owner, Integer pageSize) {
        String result = requestScanNft(owner, pageSize);
        if (!StringUtils.isEmpty(result)) {
            try {
                JSONObject jsonObject = JSON.parseObject(result);
                String data = jsonObject.get("data").toString();
                JSONObject dataJson = JSON.parseObject(data);
                NftScanBean nftScanBean = JSONObject.toJavaObject(dataJson, NftScanBean.class);
                if (null != nftScanBean && !CollectionUtils.isEmpty(nftScanBean.getContent())) {
                    UserNftCollectionDTO userNftCollectionDTO = new UserNftCollectionDTO();
                    userNftCollectionDTO.setTotal(nftScanBean.getTotal());
                    userNftCollectionDTO.setPageSize(pageSize);
                    if (!CollectionUtils.isEmpty(nftScanBean.getContent())) {
                        List<UserNftBean> userNftBeanList = nftScanBean.getContent();
                        List<UserNftDTO> userNftDTOList = new ArrayList<>();
                        userNftBeanList.forEach(p -> {
                            UserNftDTO userNftDTO = new UserNftDTO();
                            userNftDTO.setContractAddress(p.getContract_address());
                            userNftDTO.setContractName(p.getContract_name());
                            userNftDTO.setTokenId(p.getToken_id());
                            userNftDTO.setImageUri(p.getImage_uri());
                            userNftDTO.setLogoUrl(p.getImage_uri());
                            userNftDTO.setAvatarLink("https://img.x2y2.io/v2/137/" + p.getContract_address() + "/" + p.getToken_id() + "/720/image.jpg");
                            userNftDTOList.add(userNftDTO);
                        });
                        userNftCollectionDTO.setUserNftDTOList(userNftDTOList);
                    }
                    return userNftCollectionDTO;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        return null;
    }

    @Override
    public List<USDValueDTO> getValue(String address) {
        Web3j web3j = Web3j.build(new HttpService(ETHConfig.WEB3_URL));
        List<Coin> coinList = coinDao.getCoin();
        List<USDValueDTO> usdValueDTOList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(coinList)) {
            coinList.forEach(p -> {
                USDValueDTO usdValueDTO = new USDValueDTO();
                UserToken userToken =  getUsdValue(web3j,address,p.getAddress(),p.getCoinoracle(),Double.parseDouble(p.getDecimals()));
                usdValueDTO.setCoinBalance(userToken.getBalance());
                usdValueDTO.setCoinUsdValue(userToken.getUsdValue());
                usdValueDTO.setCoinName(p.getCoinname());
                usdValueDTO.setCoinLogo(p.getCoinlogo());
                usdValueDTOList.add(usdValueDTO);
            });
        }
        return usdValueDTOList;
    }

    @Override
    public Boolean placeOrder(OrderDTO orderTO) {
        try {
            orderTO.setOwner(orderTO.getOwner().toLowerCase());
            Order order = Sign(orderTO);
            order.setCreatetime(new Date());
            if(orderDao.addOrder(order)>0){
                return Boolean.TRUE;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Boolean.FALSE;
    }

    @Override
    public PageOrder getOrder(Integer pageNum, Integer pageSize) {
        PageOrder pageOrder = new PageOrder();
        PageHelper.startPage(pageNum,pageSize);
        List<Order> orderList = orderDao.getOrders();
        PageInfo<Order> pageInfo = new PageInfo(orderList);
        if(!CollectionUtils.isEmpty(pageInfo.getList())){
            List<OrderTO> orderTOList = new ArrayList<>();
            pageInfo.getList().forEach(p->{
                OrderTO orderTO = new OrderTO();
                BeanUtils.copyProperties(p,orderTO);
                orderTOList.add(orderTO);
            });
            pageOrder.setOrderTOList(orderTOList);
        }
        pageOrder.setPageNum(pageNum);
        pageOrder.setPageSize(pageSize);
        pageOrder.setTotal(pageInfo.getTotal());
        return pageOrder;
    }

    @Override
    public UserNftCollectionDTO getMyOrder(String address, Integer pageNum, Integer pageSize) {
        UserNftCollectionDTO userNftCollectionDTO = getMyNFT(address,pageSize);
        List<Order> orderList = orderDao.getMyOrders(address);
        if(!CollectionUtils.isEmpty(userNftCollectionDTO.getUserNftDTOList())&&!CollectionUtils.isEmpty(orderList)){
            userNftCollectionDTO.getUserNftDTOList().forEach(x -> {
                for(int i = 0;i < orderList.size(); i++){
                    if(x.getTokenId().equalsIgnoreCase(orderList.get(i).getTokenid()) && x.getContractAddress().equalsIgnoreCase(orderList.get(i).getNft())){
                        x.setAmount(orderList.get(i).getAmount());
                        x.setHadPlace(1);
                    }
                }

            });
        }
        return userNftCollectionDTO;
    }

    @Override
    public Boolean boughtOrder(String address, Integer orderId) {
        List<Order> orders = orderDao.getMyOrders(address);
        if(CollectionUtils.isEmpty(orders)){
            return Boolean.FALSE;
        }
        Boolean a = Boolean.FALSE;
        for (Order order : orders){
            if(order.getId()==orderId){
                a = Boolean.TRUE;
            }
        }
        if(a){
            orderDao.updateOrder(orderId);
            return Boolean.TRUE;
        }
        return Boolean.FALSE;


    }


    public String requestScanNft(String owner, Integer pageSize) {
        OkHttpClient client = new OkHttpClient().newBuilder()
                .build();
        Request request = new Request.Builder()
                .url("https://polygonapi.nftscan.com/api/v2/account/own/" + owner + "?erc_type=erc721&limit=" + pageSize)
                .method("GET", null)
                .addHeader("X-API-KEY", "mrrOqokq0NegjbpCoNsgPaiB")
                .build();
        Response response = null;
        try {
            response = client.newCall(request).execute();
            String result = response.body().string();
            return result;

        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;

    }

    private UserToken getUsdValue(Web3j web3j, String address, String token, String priceOracle, double decimals) {
        String balanceMethod = "balanceOf";
        String lastAnswerMethod = "lastAnswer";
        List<Type> inputParameters = new ArrayList<>();
        inputParameters.add(new Address(address));

        List<TypeReference<?>> outputParameters = new ArrayList<>();
        TypeReference<Uint256> typeReference1 = new TypeReference<Uint256>() {
        };

        outputParameters.add(typeReference1);
        Function function = new Function(balanceMethod, inputParameters, outputParameters);
        String data = FunctionEncoder.encode(function);
        Transaction transaction = Transaction.createEthCallTransaction(address, token, data);

        EthCall ethCall;
        try {
            ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send();
            List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());
            if (results != null && results.size() > 0) {
                BigInteger balance = (BigInteger) results.get(0).getValue();
                BigInteger lastAnswer = lastAnswer(web3j,address,priceOracle);
                BigDecimal tokenUsdt = new BigDecimal(lastAnswer).divide(new BigDecimal("100000000"),6,BigDecimal.ROUND_DOWN);
                BigDecimal tokenValue = new BigDecimal(balance).divide(new BigDecimal(Math.pow(10,decimals)),6,BigDecimal.ROUND_DOWN);
                UserToken userToken = new UserToken();
                userToken.setBalance(tokenValue.toString());
                userToken.setUsdValue(tokenUsdt.multiply(tokenValue).toString());
                if(userToken.getUsdValue().startsWith("0E")){
                    userToken.setUsdValue("0");
                }
                return userToken;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    public Order Sign(OrderDTO orderTO) throws IOException{
        Order order = new Order();
        order.setTokenid(orderTO.getTokenId());
        order.setLink(orderTO.getAvatarLink());
        order.setNft(orderTO.getNft());
        order.setOwner(orderTO.getOwner());
        order.setAmount(orderTO.getAmount());
        DexOrderDomain dexOrderDomain =
                DexOrderDomain.init("Exchange", "1", "0x8198FE4292A8420656981417Bd025d73b173a8F8");
        DexOrderDomain.Message message = new DexOrderDomain.Message();
        message.setNftAddress(order.getNft());
        message.setOwnerAddress(order.getOwner());
        message.setTokenId(new BigInteger(order.getTokenid()));
        message.setAmount(new BigInteger(order.getAmount()));
        dexOrderDomain.setMessage(message);
        String s = JSONObject.toJSONString(dexOrderDomain);
        String s1 = s.replaceAll("eIP712Domain", "EIP712Domain");
        String s2 = s1.replaceAll("\"order\"", "\"Order\"");
        System.out.println(s2);
        StructuredDataEncoder dataEncoder2 = new StructuredDataEncoder(s2);
        byte[] hashStructuredMessage2 = dataEncoder2.hashStructuredData();
        Credentials credentials2 =
                Credentials.create("d01260d2377d3b1b5062813ac6396bd02f96db76ed687044ceb1e20255d43e87");
        System.out.println(credentials2.getAddress());
        Sign.SignatureData signatureData2 = Sign.signMessage(hashStructuredMessage2,
                credentials2.getEcKeyPair(),false);
        StructuredDataEncoder dataEncoder = null;
        try {
            dataEncoder = new StructuredDataEncoder(s2);
        } catch (IOException e) {
            e.printStackTrace();
        }
        String r= Numeric.toHexString(signatureData2.getR());
        String s4= Numeric.toHexString(signatureData2.getS());
        String v = Numeric.toHexString(signatureData2.getV());
        order.setR(r);
        order.setS(s4);
        order.setV(v);
        return order;

    }

    public BigInteger lastAnswer(Web3j web3j,String fromAddress,String priceOracle) {
        String methodName = "latestAnswer";
        List<Type> inputParameters = new ArrayList<>();
        List<TypeReference<?>> outputParameters = new ArrayList<>();


        TypeReference<Uint256> typeReference1 = new TypeReference<Uint256>() {
        };

        outputParameters.add(typeReference1);
        Function function = new Function(methodName, inputParameters, outputParameters);
        String data = FunctionEncoder.encode(function);
        Transaction transaction = Transaction.createEthCallTransaction(fromAddress, priceOracle, data);

        EthCall ethCall;
        try {
            ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send();
            List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());
            if (results != null && results.size() > 0) {

                BigInteger answer = (BigInteger) results.get(0).getValue();
                return answer;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String getResource(String jsonFile) throws IOException {
        return new String(Files.readAllBytes(Paths.get(jsonFile).toAbsolutePath()), StandardCharsets.UTF_8);
    }

}
